package me.ingxin.android.sysbar

import android.view.View
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE
import androidx.core.view.WindowInsetsAnimationCompat.Callback.DispatchMode
import androidx.core.view.WindowInsetsCompat

/**
 * Created by ingxin on 2022/8/10
 *
 * 监听系统栏(导航栏和状态栏)高度变化,监听输入法弹窗高度变化
 * 将dialog style中的[android:windowIsFloating]设置为false后,可以解决dialog的window相关问题,
 * 例如沉浸式状态栏,键盘高度检测等问题.
 * ```
 *     <style name="MyDialog" parent="Theme.AppCompat.Dialog">
 *         <!-- 设置软键盘adjustResize才能正常-->
 *         <item name="android:windowSoftInputMode">adjustResize</item>
 *         <!--windowIsFloating为false时,WindowInsets和沉浸式生效-->
 *         <item name="android:windowIsFloating">false</item>
 *         <!--当windowIsFloating为false时必须设置下面两个属性,否则崩溃-->
 *         <item name="android:windowActionBar">true</item>
 *         <item name="windowActionBar">true</item>
 *      </style>
 * ```
 * @param sysBarAnchorView 触发系统栏监听的view
 */
class InsetsWatcher(private val sysBarAnchorView: View) {

    private var iMEAnchorView: View? = null
    private var iMEAnimationCompat: IMEAnimationCompat? = null
    private var getSystemBarsHeightCallback: GetSystemBarsHeightCallback? = null

    /**
     * 对同一个view[setWindowInsetsChangeListener]和[onIMEAnimationCallback]会相互覆盖，最后调用的生效
     * 需要调用[WindowCompat.setDecorFitsSystemWindows]或[immersion]开启沉浸式
     */
    private fun setWindowInsetsChangeListener(view: View) {
        view.setOnApplyWindowInsetsListener { v, insets ->
            getSystemBarsHeightCallback?.let {
                val p = v.getSystemBarsHeight()
                //唯一key
                val tagApplyStatusHeight = 766613351
                val tagApplyNavHeight = 766613352

                val lastStatusHeight = v.getTag(tagApplyStatusHeight) as? Int ?: 0
                val lastNavHeight = v.getTag(tagApplyNavHeight) as? Int ?: 0
                val currentStatusHeight = p.first
                val currentNavHeight = p.second
                if (lastStatusHeight != currentStatusHeight || lastNavHeight != currentNavHeight) {
                    v.setTag(tagApplyStatusHeight, currentStatusHeight)
                    v.setTag(tagApplyNavHeight, currentNavHeight)
                    it.onResult(p)
                }
            }
            //调整软键盘
            if (iMEAnchorView != null && iMEAnimationCompat != null) {
                adjustIME(iMEAnchorView!!, iMEAnimationCompat!!)
            }
            insets
        }
    }

    /**
     * 系统栏和导航栏高度变化时回调
     */
    fun onSystemBarsHeightCallback(callback: GetSystemBarsHeightCallback): InsetsWatcher {
        getSystemBarsHeightCallback = callback
        return this
    }

    /**
     * 设置输入法弹出动画回调,如果需要监听需要满足下面几点
     * 对同一个view[setWindowInsetsChangeListener]和[onIMEAnimationCallback]会相互覆盖，最后调用的生效
     *
     * 1.需要调用[WindowCompat.setDecorFitsSystemWindows]或[immersion]开启沉浸式
     * 2.为了更好的兼容，需要配置android:windowSoftInputMode="adjustResize"
     * 3.在onPause生命周期调用[Window.hideIME]等收起键盘，同时需要将UI还原到未弹出键盘的状态。这样能避免很多bug
     *
     * @param view 触发输入框的view
     * @param dispatchMode 监听传播模式
     * @param callback 动画回调
     */
    fun onIMEAnimationCallback(
        view: View,
        @DispatchMode dispatchMode: Int = DISPATCH_MODE_CONTINUE_ON_SUBTREE,
        callback: IMEAnimationCallback
    ): InsetsWatcher {
        if (sysBarAnchorView === view) {
            throw IllegalArgumentException("sysBarAnchorView and view can't be the same view")
        }
        val compat = IMEAnimationCompat(view, dispatchMode, callback)
        this.iMEAnchorView = view
        this.iMEAnimationCompat = compat
        return this
    }


    /**
     * 启动监听
     */
    fun launch() {
        setWindowInsetsChangeListener(sysBarAnchorView)
        if (iMEAnchorView != null && iMEAnimationCompat != null) {
            ViewCompat.setWindowInsetsAnimationCallback(iMEAnchorView!!, iMEAnimationCompat!!)
        }
    }

    /**
     * 用户调整了键盘的高度
     */
    private fun adjustIME(view: View, imeCompat: IMEAnimationCompat) {
        if (imeCompat.inProgress) {
            return
        }
        val insets = ViewCompat.getRootWindowInsets(view)
        val isVisible = insets?.isVisible(WindowInsetsCompat.Type.ime()) ?: return
        //当前输入法正在显示
        if (isVisible && imeCompat.isStarted()) {
            val h = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
            imeCompat.adjustOnShow(h)
        }
    }


}